package crp.services.api.graphql.loaders;

import com.qwlabs.lang.C2;
import crp.core.course.app.CourseService;
import crp.core.course.app.PlanCourseService;
import crp.core.course.domain.models.CourseAttributesEntity;
import crp.core.course.domain.models.CourseFacultyEntity;
import crp.core.course.domain.models.CourseFacultyRole;
import crp.core.course.domain.models.PlanCourseEntity;
import crp.core.resource.app.CourseResourceService;
import crp.core.resource.app.ResourceService;
import crp.core.resource.domain.models.CourseResourceCount;
import crp.core.resource.domain.models.CourseResourceEntity;
import crp.core.resource.domain.models.ResourceQuery;
import crp.services.api.graphql.models.CourseAttributesPayload;
import crp.services.api.graphql.models.CourseFacultyPayload;
import crp.services.api.graphql.models.CoursePayload;
import crp.services.api.graphql.models.PlanCoursePayload;
import crp.services.api.graphql.models.ResourceFilterInput;
import crp.services.api.graphql.models.ResourcePayload;
import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Source;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

@GraphQLApi
@RequestScoped
public class CoursePayloadLoader {
    private final CourseService courseService;
    private final PlanCourseService subjectService;
    private final CourseResourceService courseResourceService;
    private final ResourceService resourceService;

    @Inject
    public CoursePayloadLoader(CourseService courseService,
                               PlanCourseService subjectService,
                               CourseResourceService courseResourceService,
                               ResourceService resourceService) {
        this.courseService = courseService;
        this.subjectService = subjectService;
        this.courseResourceService = courseResourceService;
        this.resourceService = resourceService;
    }

    @NotNull
    public List<@NotNull PlanCoursePayload> planCourse(@Source List<CoursePayload> courses) {
        Set<String> subjectIds = C2.set(courses, CoursePayload::getPlanCourseId);
        Map<String, PlanCoursePayload> subjects = C2.map(
                subjectService.searcher().byIds(subjectIds),
                PlanCourseEntity::getId,
                PlanCoursePayload::of);
        return C2.list(courses, course -> subjects.get(course.getPlanCourseId()));
    }

    @NotNull
    public List<@NotNull CourseAttributesPayload> attributes(@Source List<CoursePayload> courses) {
        Set<String> courseIds = C2.set(courses, CoursePayload::getId);
        Map<String, CourseAttributesPayload> attributes = C2.map(
                courseService.searcher().attributesByIds(courseIds),
                CourseAttributesEntity::getCourseId,
                CourseAttributesPayload::of);
        return C2.list(courses, course ->
                attributes.getOrDefault(course.getId(), CourseAttributesPayload.empty()));
    }

    @NotNull
    public List<@NotNull List<@NotNull ResourcePayload>> resources(@Source List<CoursePayload> courses,
                                                                   ResourceFilterInput filter) {
        Set<String> courseIds = C2.set(courses, CoursePayload::getId);
        ResourceQuery resourceFilter = Optional.ofNullable(filter).map(ResourceFilterInput::of).orElse(null);
        Map<String, List<ResourcePayload>> courseResourceMap = C2.listMap(courseResourceService.searcher().by(courseIds, resourceFilter),
                CourseResourceEntity::getCourseId,
                cr -> ResourcePayload.of(cr.getResource()));

        return C2.list(courses, c -> courseResourceMap.getOrDefault(c.getId(), List.of()));
    }

    @NotNull
    public List<@NotNull Long> resourceCount(@Source List<CoursePayload> courses) {
        Set<String> courseIds = C2.set(courses, CoursePayload::getId);
        Map<String, Long> counts = C2.map(
                resourceService.searcher().countByCourseIds(courseIds),
                CourseResourceCount::getCourseId,
                CourseResourceCount::getResourceCount
        );
        return C2.list(courses, course -> counts.getOrDefault(course.getId(), 0L));
    }

    @NotNull
    public List<@NotNull Long> publicResourceCount(@Source List<CoursePayload> courses) {
        Set<String> courseIds = C2.set(courses, CoursePayload::getId);
        Map<String, Long> counts = C2.map(
                resourceService.searcher().publicCountByCourseIds(courseIds),
                CourseResourceCount::getCourseId,
                CourseResourceCount::getResourceCount
        );
        return C2.list(courses, course -> counts.getOrDefault(course.getId(), 0L));
    }

    @NotNull
    public List<@NotNull Long> publishedResourceCount(@Source List<CoursePayload> courses) {
        Set<String> courseIds = C2.set(courses, CoursePayload::getId);
        Map<String, Long> counts = C2.map(
                resourceService.searcher().publishedCountByCourseIds(courseIds),
                CourseResourceCount::getCourseId,
                CourseResourceCount::getResourceCount
        );
        return C2.list(courses, course -> counts.getOrDefault(course.getId(), 0L));
    }

    public List<@NotNull List<@NotNull CourseFacultyPayload>> faculties(@Source List<CoursePayload> courses,
                                                                        Set<CourseFacultyRole> roles) {
        Set<String> courseIds = C2.set(courses, CoursePayload::getId);
        Map<String, List<CourseFacultyPayload>> faculties = C2.listMap(
                courseService.searcher().facultiesByIds(courseIds, roles),
                CourseFacultyEntity::getCourseId,
                CourseFacultyPayload::of);
        return C2.list(courses, course -> faculties.getOrDefault(course.getId(), List.of()));
    }
}
